package com.gitee.l0km.codegen.decorator;

import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestParam;

import com.gitee.l0km.codegen.annotations.AnnotationException;
import com.gitee.l0km.codegen.annotations.AnnotationRuntimeException;
import com.gitee.l0km.codegen.annotations.DefaultGenericTypes;
import com.gitee.l0km.codegen.annotations.DeriveMethod;
import com.gitee.l0km.codegen.annotations.Generic;
import com.gitee.l0km.codegen.annotations.GenericNameException;
import com.gitee.l0km.codegen.annotations.GenericParam;
import com.gitee.l0km.codegen.annotations.GenericParamException;
import com.gitee.l0km.codegen.base.AnnotationUtils;
import com.gitee.l0km.codegen.base.CodeGenUtils;
import com.gitee.l0km.codegen.base.InvalidAnnotationDefineException;
import com.gitee.l0km.codegen.base.InvalidNameException;
import com.gitee.l0km.codegen.base.Method;
import com.gitee.l0km.codegen.base.NewSourceInfoAbstract;
import com.gitee.l0km.codegen.base.ServiceInfo;
import com.gitee.l0km.codegen.base.Method.Parameter;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

import static com.google.common.base.Preconditions.*;

/**
 * decorator生成器{@code NewSourceInfo}实现
 * @author guyadong
 *
 * @param <T>
 */
public class Decorator<T> extends NewSourceInfoAbstract<T> {
	private static final Logger logger = LoggerFactory.getLogger(Decorator.class);
	private DefaultParameterGenericTypes defaultGenericTypes;
	private ServiceInfo serviceInfo;
	private List<Method> needGenericMethods;
	private final Set<Class<?>> localResolvedTypes = new LinkedHashSet<Class<?>>();
	private final Set<Class<?>> remoteResolveTypes = new LinkedHashSet<Class<?>>();
	private String portPrefix;
	public Decorator(Class<T> interfaceClass, Class<? extends T> refClass) {
		super(interfaceClass, refClass, null);
		// 将 RequestMapping 注释排除
		getAnnHelper().getExcludeAnnotations().add("org.springframework.web.bind.annotation.RequestMapping");
		portPrefix = "/" + interfaceClass.getSimpleName() + "/";
	}

	@Override
	protected void addImportedClassFromMethod(Method method, 
			boolean importExceptions, boolean importReturn, boolean importAnnotation) {
		super.addImportedClassFromMethod(method, importExceptions, importReturn, importAnnotation);
		
		if(importAnnotation) {
			// 将方法中的注释引用的类添加到import class中
			addImportedClass(getAnnHelper().annotationClassOf(method, true, "SUPERCLASS"));
		}
	}

	@Override
	public boolean compile() {
		boolean compileOk=false;
		try{
			defaultGenericTypes = new DefaultParameterGenericTypes(interfaceClass);
			serviceInfo=new ServiceInfo(AnnotationUtils.getServiceAnnotation(interfaceClass));
			super.compile();
			checkDuplicatedGenericName();
			for (Method method : methodsNeedGenerated) {
				compile(method);
			}
			compileOk = true;
		} catch (AnnotationException e) {
			logger.error(e.toString());
		} catch(AnnotationRuntimeException e){
			logger.error(e.toString());
		} 
		return compileOk;
	}
	private final void compile(Method method) 
			throws GenericParamException, GenericNameException, GenericNameConflictException {
		for (Parameter param : method.getParameters()) {
			try {
				compile(param);
			} catch (GenericParamRedefineException e) {
				logger.warn("{} is generic type({}) defined by class,can't be redefined in method:\n{}", param.name,
						param.genericType.toString(), method.toGenericString());
			} catch (GenericNameException e) {
				throw new GenericNameException(String.format(
						"the name of annotation %s of parameter [%s] must not have space char in method %s",
						Generic.class.getSimpleName(), param.name, method.toGenericString()));
			}
		}
		DeriveMethod deriveMethodAnnotation = getDeriveMethodAnnotation(method);
		if(deriveMethodAnnotation!=null){
			this.localResolvedTypes.addAll(Arrays.asList(deriveMethodAnnotation.localResolvedTypes()));
			this.remoteResolveTypes.addAll(Arrays.asList(deriveMethodAnnotation.remoteResolveTypes()));
		}
	}

	private final void compile(Parameter param) throws 
			GenericParamRedefineException, GenericNameConflictException,	GenericNameException {
		GenericParam genericParam = param.getAnnotation(GenericParam.class);
		if (null != genericParam 
				&& genericParam.value() 
				&& (param.genericType instanceof TypeVariable)) {
			throw new GenericParamRedefineException();
		}
		if (null != genericParam && !genericParam.name().isEmpty()) {
			if (!Pattern.compile("^[A-Z]\\w*$").matcher(genericParam.name()).matches()){
				throw new GenericNameException();
			}
			if (hasInClassGenericTypes(genericParam.name())){
				throw new GenericNameConflictException(
						genericParam.name(), 
						GenericParam.class.getSimpleName(),
						genericParam.toString());
			}
		}
	}
	@Override
	protected void checkClass(Class<T> interfaceClass, Class<? extends T> refClass, Class<? extends T> baseClass) {
		checkNotNull(interfaceClass,"interfaceClass is null");
		if (!interfaceClass.isInterface()){
			throw new IllegalArgumentException("interfaceClass must be Interface(必须是接口)");
		}
		if(null != refClass ){
			if ( !interfaceClass.isAssignableFrom(refClass) 
					|| refClass.isInterface()){
				throw new IllegalArgumentException(String.format(
						"refClass must  implement of [%s] (必须实现接口)", interfaceClass.getName()));
			}
			if (!isFullImplemented(interfaceClass,refClass)){
				logger.warn("{} not  implement all methods of [{}] (没有实现接口所有方法)",
						refClass.getName(),
						interfaceClass.getName());
			}
		}
	}

	@Override
	protected void createMethodsNeedGenerated() {
		List<Method> methods = Lists.transform(Arrays.asList(interfaceClass.getMethods()), 
				new Function<java.lang.reflect.Method, Method>() {
					@Override
					public Method apply(java.lang.reflect.Method input) {
						return new Method(input,
								paramTable.getParameterNamesUnchecked(input.getName(), input.getParameterTypes()));
					}
		});
		needGenericMethods=ImmutableList.copyOf(Iterables.filter(methods, 
				new Predicate<Method>(){
					@Override
					public boolean apply(Method input) {
						return needGeneric(input);
					}}));
		methodsNeedGenerated.addAll(methods);
	}

	public DefaultParameterGenericTypes getDefaultGenericTypes() {
		return defaultGenericTypes;
	}
	public String getDefaultGenericTypeNames(){
		return Joiner.on(",").join(Iterables.transform(defaultGenericTypes.types(), new Function<Class<?>,String>(){
			@Override
			public String apply(Class<?> input) {
				return getTypeName(input);
			}}));
	}
	public ServiceInfo getServiceInfo() {
		return serviceInfo;
	}
	public boolean isTargetType(Class<?> type){
		return serviceInfo.getTargetType()==type;
	}
	private boolean hasInClassGenericTypes(String name) {
		for (TypeVariable<Class<T>> type : interfaceClass.getTypeParameters()) {
			if (type.getName().equals(name))
				return true;
		}
		return false;
	}

	private final boolean hasNeedGenericParameter(Method method) {
		for (Parameter param : method.getParameters()) {
			if (needGeneric(param)) {
				return true;
			}
		}
		return false;
	}
	public boolean needGeneric(Method method) {
		Generic generic = method.getAnnotation(Generic.class);
		return (null == generic || generic.value()) && hasNeedGenericParameter(method);
	}
	public final boolean needGeneric(Parameter param){
		GenericParam genericParam = param.getAnnotation(GenericParam.class);
		return (null == genericParam || genericParam.value()) && defaultGenericTypes.hasType(param.type);
	}
	/**
	 *  检查是否有同名的泛型名称
	 */
	private void checkDuplicatedGenericName(){
		for (String name : defaultGenericTypes.names()) {
			if (hasInClassGenericTypes(name))
				try {
					throw new GenericNameConflictException(name, DefaultGenericTypes.class.getSimpleName(),
							defaultGenericTypes.toString());
				} catch (GenericNameConflictException e) {
					throw new DefaultGenericTypesExceptoin(e);
				}
		}
	}
	public DeriveMethod getDeriveMethodAnnotation(Method method) {
		try {
			return AnnotationUtils.getDeriveMethodAnnotation(method, serviceInfo);
		} catch (InvalidNameException e) {
			throw new RuntimeException(e);
		} catch (InvalidAnnotationDefineException e) {
			throw new RuntimeException(e);
		}
	}
	public final Set<String> getGenericNames(Method method) {
		GenericParam genericParam;
		Set<String> names=new HashSet<String>();
		for (Parameter param : method.getParameters()) {
			if (needGeneric(param)) {
				if(null==(genericParam = getGenericParam(param))){					
					names.add(defaultGenericTypes.getName(param.getType()));
				}else
					names.add(genericParam.name());
			}
		}
		return names;
	}
	public final GenericParam getGenericParam(Parameter param){
		return  param.getAnnotation(GenericParam.class);
	}
	public boolean needDeriveParam(Method method,Parameter param){
		DeriveMethod deriveMethod =getDeriveMethodAnnotation(method);
		if(deriveMethod!=null){
			Set<String> set = CodeGenUtils.toSet(deriveMethod.genericParam());
			return needGeneric(param)&&(set.isEmpty()||set.contains(param.name));
		}
		return false;
	}
	public ArrayList<String> getDeriveParameterNames(Method method){
		ArrayList<String> list = new ArrayList<String>();
		for(Parameter parameter:method.getParameters()){
			if(needDeriveParam(method,parameter)){
				list.add(parameter.name);
			}
		}
		return list;
	}
	public boolean isHasRemoteResolveType() {
		return !remoteResolveTypes.isEmpty();
	}

	public List<Method> getNeedGenericMethods() {
		return needGenericMethods;
	}

	public List<Class<?>> getLocalResolvedTypes() {
		return new ArrayList<Class<?>>(localResolvedTypes);
	}

	public List<Class<?>> getRemoteResolveTypes() {
		return new ArrayList<Class<?>>(remoteResolveTypes);
	}

	/**
	 * 返回 {@link DeriveMethod} 注释指定的方法名后缀,没有则返回{@code null}
	 * @param method
	 */
	public String methodSuffix(Method method){
		DeriveMethod deriveMethod = getDeriveMethodAnnotation(method);
		if(deriveMethod !=null ){
			String[] suffixs = deriveMethod.methodSuffix();
			return suffixs != null && suffixs.length>0 ? suffixs[0]:"";
		}
		return "";
	}
	public Decorator<T> setExcludeAnnotations(Set<String> excludeAnnotations) {
		if(null != excludeAnnotations){
			getAnnHelper().getExcludeAnnotations().addAll(excludeAnnotations);			
		}
		return this;
	}
	public Decorator<T> setPortPrefix(String portPrefix) {
		if(!Strings.isNullOrEmpty(portPrefix)) {
			this.portPrefix = portPrefix;
		}
		return this;
	}
	public String getPortPrefix() {
		return portPrefix;
	}
	public String requestMethodOf(Method method, String defaultValue){
		return getAnnHelper().requestMethodOf(method,defaultValue);
	}
	public String producesOf(Method method){
		return getAnnHelper().producesOf(method);
	}
	public String appendProducesIfNoempty(Method method){
		return getAnnHelper().appendProducesIfNoempty(method);
	}
	public boolean needResponse(Method method){
		return getAnnHelper().needResponse(method);
	}
	public String defaultValueCodeOf(Parameter parameter,String left){
		return getAnnHelper().defaultValueCodeOf(parameter,left);
	}
	/**
	 * 根据参数类型和参数注释计算参数是否为必须的
	 * @param parameter
	 * @return true if required,otherwise false
	 */
	public boolean isRequired(Parameter parameter) {
		if(null != parameter) {
			if(parameter.getType().isPrimitive()) {
				return true;
			}
			if(null != parameter.getAnnotation("NotNull")) {
				return true;
			}
			if(null != parameter.getAnnotation("Nullable")) {
				return false;
			}
			RequestParam rpa = parameter.getAnnotation(RequestParam.class);
			if(null != rpa){
				return rpa.required();
			}
		}
		return false;
	}
	public static final String ISLOCAL_METHOD_NAME = "isLocal";
	public static boolean isIsLocalMethod(Method method){
		if(null == method){
			return false;
		}
		return method.getName().equals(ISLOCAL_METHOD_NAME)
				&& method.getParameterTypes().length == 0 
				&& method.getExceptionTypes().length == 0
				&& method.getReturnType() == boolean.class;
	}
}
